#pragma once

#include <cv.h>

namespace NumberPlateReader 
{
	//Replaced my square detection with the OpenCV sample since it is more accurate.

	//Also found a GREAT iplImage to BITMAP converter library from the following link.
	//Currently used as is, should check to see if it could be optimised.
	/*!
	* \brief
	* The Code in this file was copy from OpenCVDotNet Project
	* http://code.google.com/p/opencvdotnet/
	*/
	ref class Utils
	{
	private:

		static void FillBitmapInfo( BITMAPINFO* bmi, int width, int height, int bpp, int origin )
		{
			assert( bmi && width >= 0 && height >= 0 && (bpp == 8 || bpp == 24 || bpp == 32));

			BITMAPINFOHEADER* bmih = &(bmi->bmiHeader);

			memset( bmih, 0, sizeof(*bmih));
			bmih->biSize = sizeof(BITMAPINFOHEADER);
			bmih->biWidth = width;
			bmih->biHeight = origin ? abs(height) : -abs(height);
			bmih->biPlanes = 1;
			bmih->biBitCount = (unsigned short)bpp;
			bmih->biCompression = BI_RGB;

			if( bpp == 8 )
			{
				RGBQUAD* palette = bmi->bmiColors;
				int i;
				for( i = 0; i < 256; i++ )
				{
					palette[i].rgbBlue = palette[i].rgbGreen = palette[i].rgbRed = (BYTE)i;
					palette[i].rgbReserved = 0;
				}
			}
		}

	public:
		static Bitmap^ IplImageToBitmap(IplImage *sourceImage)
		{
			int height = 0;
			int width = 0;
			int channels = 0;
			void* dst_ptr = 0;
			const int channels0 = 3;
			int origin = 0;
			CvMat stub, dst, *image;
			bool changed_size = false; // philipg

			HDC hdc = CreateCompatibleDC(0);
			CvArr* arr = (CvArr*)sourceImage;

			if (CV_IS_IMAGE_HDR(arr)) origin = ((IplImage*)arr)->origin;

			image = cvGetMat(arr, &stub);

			uchar buffer[sizeof(BITMAPINFO) + 255*sizeof(RGBQUAD)];
			BITMAPINFO* binfo = (BITMAPINFO*)buffer;

			width = image->width;
			height = image->height;
			channels = channels0;

			FillBitmapInfo(binfo, width, height, channels*8, 1);

			HBITMAP hBitmap = CreateDIBSection(hdc, binfo, DIB_RGB_COLORS, &dst_ptr, 0, 0);
			if (hBitmap == NULL)
				return nullptr;

			cvInitMatHeader(&dst, height, width, CV_8UC3, dst_ptr, (width * channels + 3) & -4);
			cvConvertImage(image, &dst, origin == 0 ? CV_CVTIMG_FLIP : 0);

			System::Drawing::Bitmap^ bmpImage = System::Drawing::Image::FromHbitmap(IntPtr(hBitmap));

			DeleteObject(hBitmap);
			DeleteDC(hdc);

			return bmpImage;
		}

		static void StringToCharPointer(String^ string, char* output, int size)
		{
			pin_ptr<const __wchar_t> p = PtrToStringChars(string);
			wcstombs_s(NULL, output, size, p, size);
		}

		// returns sequence of squares detected on the image.
		// the sequence is stored in the specified memory storage
		static CvSeq* findSquares4( IplImage* img, CvMemStorage* storage )
		{
			CvSeq* contours;
			int i, c, l, N = 11;
			int thresh = 30;
			CvSize sz = cvSize( img->width & -2, img->height & -2 );
			IplImage* timg = cvCloneImage( img ); // make a copy of input image
			IplImage* gray = cvCreateImage( sz, 8, 1 ); 
			IplImage* pyr = cvCreateImage( cvSize(sz.width/2, sz.height/2), 8, 3 );
			IplImage* tgray;
			CvSeq* result;
			double s, t;
			// create empty sequence that will contain points -
			// 4 points per square (the square's vertices)
			CvSeq* squares = cvCreateSeq( 0, sizeof(CvSeq), sizeof(CvPoint), storage );

			// select the maximum ROI in the image
			// with the width and height divisible by 2
			cvSetImageROI( timg, cvRect( 0, 0, sz.width, sz.height ));

			// down-scale and upscale the image to filter out the noise
			cvPyrDown( timg, pyr, 7 );
			cvPyrUp( pyr, timg, 7 );
			tgray = cvCreateImage( sz, 8, 1 );

			// find squares in every color plane of the image
			for( c = 0; c < 3; c++ )
			{
				// extract the c-th color plane
				cvSetImageCOI( timg, c+1 );
				cvCopy( timg, tgray, 0 );

				// try several threshold levels
				for( l = 0; l < N; l++ )
				{
					// hack: use Canny instead of zero threshold level.
					// Canny helps to catch squares with gradient shading   
					if( l == 0 )
					{
						// apply Canny. Take the upper threshold from slider
						// and set the lower to 0 (which forces edges merging) 
						cvCanny( tgray, gray, 0, thresh, 5 );
						// dilate canny output to remove potential
						// holes between edge segments 
						cvDilate( gray, gray, 0, 1 );
					}
					else
					{
						cvThreshold( tgray, gray, (l+1)*255/N, 255, CV_THRESH_BINARY );
					}

					// find contours and store them all as a list
					cvFindContours( gray, storage, &contours, sizeof(CvContour),
						CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE, cvPoint(0,0) );

					// test each contour
					while( contours )
					{
						// approximate contour with accuracy proportional
						// to the contour perimeter
						result = cvApproxPoly( contours, sizeof(CvContour), storage,
							CV_POLY_APPROX_DP, cvContourPerimeter(contours)*0.02, 0 );
						// square contours should have 4 vertices after approximation
						// relatively large area (to filter out noisy contours)
						// and be convex.
						// Note: absolute value of an area is used because
						// area may be positive or negative - in accordance with the
						// contour orientation
						if( result->total == 4 &&
							fabs(cvContourArea(result,CV_WHOLE_SEQ)) > 1000 &&
							cvCheckContourConvexity(result) )
						{
							s = 0;

							for( i = 0; i < 5; i++ )
							{
								// find minimum angle between joint
								// edges (maximum of cosine)
								if( i >= 2 )
								{
									t = fabs(Utils::angle(
										(CvPoint*)cvGetSeqElem( result, i ),
										(CvPoint*)cvGetSeqElem( result, i-2 ),
										(CvPoint*)cvGetSeqElem( result, i-1 )));
									s = s > t ? s : t;
								}
							}

							// if cosines of all angles are small
							// (all angles are ~90 degree) then write quandrange
							// vertices to resultant sequence 
							if( s < 0.3 )
								for( i = 0; i < 4; i++ )
									cvSeqPush( squares,
									(CvPoint*)cvGetSeqElem( result, i ));
						}

						// take the next contour
						contours = contours->h_next;
					}
				}
			}

			// release all the temporary images
			cvReleaseImage( &gray );
			cvReleaseImage( &pyr );
			cvReleaseImage( &tgray );
			cvReleaseImage( &timg );

			return squares;
		}

		// helper function:
		// finds a cosine of angle between vectors
		// from pt0->pt1 and from pt0->pt2 
		static double angle( CvPoint* pt1, CvPoint* pt2, CvPoint* pt0 )
		{
			double dx1 = pt1->x - pt0->x;
			double dy1 = pt1->y - pt0->y;
			double dx2 = pt2->x - pt0->x;
			double dy2 = pt2->y - pt0->y;
			return (dx1 * dx2 + dy1 * dy2) / sqrt((dx1 * dx1 + dy1 * dy1) *( dx2 * dx2 + dy2 * dy2) + 1e-10);
		}

		// the function draws all the squares in the image
		static IplImage* drawSquares( IplImage* img, CvSeq* squares )
		{
			CvSeqReader reader;
			IplImage* cpy = cvCloneImage( img );
			int i;

			// initialize reader of the sequence
			cvStartReadSeq( squares, &reader, 0 );

			// read 4 sequence elements at a time (all vertices of a square)
			for( i = 0; i < squares->total; i += 4 )
			{
				CvPoint pt[4], *rect = pt;
				int count = 4;

				// read 4 vertices
				CV_READ_SEQ_ELEM( pt[0], reader );
				CV_READ_SEQ_ELEM( pt[1], reader );
				CV_READ_SEQ_ELEM( pt[2], reader );
				CV_READ_SEQ_ELEM( pt[3], reader );

				//There is no distinct starting point so have to check witch one to use.
				CvPoint topLeftPoint = cvPoint( 10000, 50000 );
				CvPoint bottomLeftPoint = cvPoint( 10000, -5000 );
				CvPoint topRightPoint = cvPoint( -10000, 5000 );
				CvPoint bottomRightPoint = cvPoint( -10000, -5000 );

				for( int x = 0; x < 4; x++ )
				{
					//Bottom Left
					if( rect[x].x < bottomLeftPoint.x && rect[x].y > bottomLeftPoint.y )
					{
						bottomLeftPoint.x = rect[x].x;
						bottomLeftPoint.y = rect[x].y;
					}

					//Bottom Right
					if( rect[x].x >  bottomRightPoint.x && rect[x].y > bottomRightPoint.y )
					{
						bottomRightPoint.x = rect[x].x;
						bottomRightPoint.y = rect[x].y;
					}

					//Top Left
					if( rect[x].x < topLeftPoint.x && rect[x].y > topLeftPoint.y )
					{
						topLeftPoint.x = rect[x].x;
						topLeftPoint.x = rect[x].y;
					}

					//Top Right
					if( rect[x].x > topRightPoint.x && rect[x].y < topRightPoint.y )
					{
						topRightPoint.x = rect[x].x;
						topRightPoint.y = rect[x].y;
					}
				}

				cvSetImageROI( cpy, cvRect( bottomLeftPoint.x, bottomLeftPoint.y, abs( bottomLeftPoint.x - bottomRightPoint.x ), abs( bottomRightPoint.y - topRightPoint.y)) );

				//If it is too small, dont look at it.
				if( cpy->roi->height > 2 && cpy->roi->width > 2 )
				{
					IplImage *smallSquare = cvCreateImage(cvSize(cpy->roi->width, cpy->roi->height), cpy->depth, cpy->nChannels);
					cvCopy(cpy, smallSquare, 0);
					String ^dateString = gcnew String(Application::StartupPath + "\\RectangleImages\\Rectangle" + DateTime::Now.Minute.ToString() + DateTime::Now.Millisecond.ToString() + i.ToString()  + ".bmp");
					const char* dateCharsPtr = (char*)Marshal::StringToHGlobalAnsi(dateString).ToPointer();

					cvFlip( smallSquare, 0, 0);

					//More processing needs to occur here.
					double scale = 2.3;
					IplImage* gray = cvCreateImage( cvSize(smallSquare->width,smallSquare->height), 8, 1 );
					IplImage* small_img = cvCreateImage( cvSize( cvRound (smallSquare->width * scale),
						cvRound (smallSquare->height * scale)),
						8, 1 );
					int i;
					//Reduce color depth (Grayscale);
					cvCvtColor( smallSquare, gray, CV_BGR2GRAY );
					//Denoise

					//Resize
					cvResize( gray, small_img, CV_INTER_LINEAR );
					//Normalise
					//cvEqualizeHist( small_img, small_img );
					//Remove border

					//Level / Rotate

					cvSaveImage( dateCharsPtr, small_img);
					cvReleaseImage( &smallSquare);
					cvReleaseImage( &small_img );
					cvReleaseImage( &gray);
				}

				cvResetImageROI(cpy);
			}

			String ^amountString = gcnew String((squares->total / 4).ToString());
			const char* AmountCharsPtr = (char*)Marshal::StringToHGlobalAnsi(amountString).ToPointer();
			CvFont *aFont = new CvFont();
			cvInitFont( aFont, CV_FONT_HERSHEY_PLAIN, 1, 1, 0, 1, 8);
			cvPutText( cpy, AmountCharsPtr, cvPoint(10,10), aFont , CV_RGB(255,0,0));

			cvCopyImage(cpy, img);
			cvReleaseImage( &cpy );
			return img;
		}
	};
}